{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Parte 2: Servindo o Modelo de forma segura com Syft Keras"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Agora que você tem um modelo treinado com Keras normal, está pronto para servir algumas previsões privadas. Podemos fazer isso usando o Syft Keras.\n",
    "\n",
    "Para proteger e servir esse modelo, precisaremos de três TFEWorkers (servidores). Isso ocorre porque o TF Encrypted por debaixo dos panos usa uma técnica de criptografia chamada [multi-party computation (MPC)](https://en.wikipedia.org/wiki/Secure_multi-party_computation). A idéia é dividir os pesos do modelo e dados de entrada em partições, depois enviar uma parte de cada valor para os diferentes servidores. A propriedade-chave é que, se você observar a parte recebida em um servidor, ele não revelará nada sobre o valor original (dados de entrada ou pesos do modelo).\n",
    "\n",
    "Definiremos um modelo Syft Keras como fizemos no notebook anterior. No entanto, há um truque: antes de instanciar esse modelo, executaremos `hook = sy.KerasHook(tf.keras)`. Isso adicionará três novos métodos importantes à classe Sequencial Keras:\n",
    " - `share`: protegerá seu modelo via compartilhamento secreto; por padrão, ele usará o protocolo SecureNN do TF Encrypted para compartilhar seu modelo em segredo entre cada um dos três TFEWorkers. Mais importante, isso adicionará a capacidade de fornecer previsões sobre dados criptografados.\n",
    " - `serve`: esta função irá lançar uma fila para servir, de modo que os TFEWorkers pode aceitar pedidos de previsão sobre o modelo seguro de clientes externos.\n",
    " - `shutdown_workers`: depois de fornecer previsões particulares, você pode desligar o modelo executando esta função. Ele o instruirá a encerrar os processos do servidor manualmente, se você optar por gerenciar manualmente cada worker.\n",
    "\n",
    "Se você quiser saber mais sobre o MPC, pode ler este excelente [blog](https://mortendahl.github.io/2017/04/17/private-deep-learning-with-mpc/)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import tensorflow as tf\n",
    "from tensorflow.keras import Sequential\n",
    "from tensorflow.keras.layers import AveragePooling2D, Conv2D, Dense, Activation, Flatten, ReLU, Activation\n",
    "\n",
    "import syft as sy\n",
    "hook = sy.KerasHook(tf.keras)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Modelo"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Como você pode ver, definimos quase exatamente o mesmo modelo de antes, exceto que fornecemos um `batch_input_shape`. Isso permite que o TF Encrypted otimize melhor os cálculos seguros através de formas predefinidas de tensores. Para esta demonstração MNIST, enviaremos dados de entrada com a forma (1, 28, 28, 1). \n",
    "Também retornamos o logit em vez de softmax porque essa operação é complexa para executar usando MPC e não precisamos dela para atender a solicitações de previsão."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "num_classes = 10\n",
    "input_shape = (1, 28, 28, 1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "model = Sequential()\n",
    "\n",
    "model.add(Conv2D(10, (3, 3), batch_input_shape=input_shape))\n",
    "model.add(AveragePooling2D((2, 2)))\n",
    "model.add(Activation('relu'))\n",
    "model.add(Conv2D(32, (3, 3)))\n",
    "model.add(AveragePooling2D((2, 2)))\n",
    "model.add(Activation('relu'))\n",
    "model.add(Conv2D(64, (3, 3)))\n",
    "model.add(AveragePooling2D((2, 2)))\n",
    "model.add(Activation('relu'))\n",
    "model.add(Flatten())\n",
    "model.add(Dense(num_classes, name=\"logit\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Carregando pesos pré-treinados"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Com a função `load_weights`, você pode carregar facilmente os pesos que você salvou anteriormente após treinar seu modelo."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pre_trained_weights = 'short-conv-mnist.h5'\n",
    "model.load_weights(pre_trained_weights)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Lançando os workers"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Vamos agora criar TFEWorkers (`alice`,` bob` e `carol`) exigidos pelo TF Encrypted para realizar previsões privadas. Para cada TFEWorker, você apenas precisa especificar um host. Em seguida, fazemos combinar esses workers em um cluster.\n",
    "\n",
    "Esses trabalhadores executam um [servidor TensorFlow](https://www.tensorflow.org/api_docs/python/tf/distribute/Server), que você pode gerenciar manualmente (`AUTO = False`) ou pedir aos workers que gerenciem para você (`AUTO = True`). Se optar por gerenciá-los manualmente, você será instruído a executar um comando de terminal no dispositivo host de cada trabalhador após chamar `cluster.start()` abaixo. Se todos os workers estiverem hospedados em um único dispositivo (por exemplo, `localhost`), você poderá escolher que o Syft gerencie automaticamente o servidor TensorFlow do worker."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "AUTO = False\n",
    "\n",
    "alice = sy.TFEWorker(host='localhost:4000', auto_managed=AUTO)\n",
    "bob = sy.TFEWorker(host='localhost:4001', auto_managed=AUTO)\n",
    "carol = sy.TFEWorker(host='localhost:4002', auto_managed=AUTO)\n",
    "\n",
    "cluster = sy.TFECluster(alice, bob, carol)\n",
    "cluster.start()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Proteja o modelo, compartilhando os pesos"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Graças ao `sy.KerasHook (tf.keras)`, você pode chamar o método `share` para transformar seu modelo em um modelo TF Encrypted Keras.\n",
    "\n",
    "Se você pediu para gerenciar manualmente os servidores acima, essa etapa não será concluída até que todos tenham sido iniciados. Observe que seu firewall pode solicitar que o Python aceite a conexão de entrada."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "model.share(cluster)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Servindo o modelo"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Perfeito! Agora, chamando `model.serve`, seu modelo está pronto para fornecer algumas previsões privadas. Você pode definir `num_requests` para definir um limite para o número de requisições de previsões atendidas pelo modelo; se não especificado, o modelo será servido até ser interrompido."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "model.serve(num_requests=3)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Você está pronto para ir para o notebook **Parte 13c** para fazer requisições de algumas previsões privadas."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Hora da limpeza!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Quando o limite de suas requisições for totalmente utilizado, o modelo não estará mais disponível para atender requisições, mas ainda será compartilhado em segredo entre os três workers acima. Você pode matar os workers executando a célula abaixo.\n",
    "\n",
    "**Parabéns** por concluir a Parte 13b: Classificação segura com Syft Keras e TFE!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "model.stop()\n",
    "cluster.stop()\n",
    "\n",
    "if not AUTO:\n",
    "    process_ids = !ps aux | grep '[p]ython -m tf_encrypted.player --config' | awk '{print $2}'\n",
    "    for process_id in process_ids:\n",
    "        !kill {process_id}\n",
    "        print(\"Process ID {id} has been killed.\".format(id=process_id))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
